package com.evolved.automata.experimental;
import com.evolved.automata.*;


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.util.*;

public class GeneManager implements Serializer {
	private int j_PoolSize;
	private LinkedList<WeightedValue<Genome>> j_Genomes;
	private int j_SelectionWidth;
	private int j_ResampleSize;
	private int j_InitialFitness=0;
	
	// SelectionWidth is the size of the total fitness of all agents in resampleSize and should be 
	// several times the resampleSize
	
	public GeneManager(int poolSize, int resampleSize, int selectionWidth)
	{
		j_SerializerHelper = new SerializerAdapter(this);
		j_PoolSize=poolSize;
		j_Genomes = new LinkedList<WeightedValue<Genome>>();
		j_SelectionWidth=selectionWidth;
		j_ResampleSize = resampleSize;
	}
	
	public GeneManager(int poolSize, int resampleSize)
	{
		j_SerializerHelper = new SerializerAdapter(this);
		j_PoolSize=poolSize;
		j_Genomes = new LinkedList<WeightedValue<Genome>>();
		j_SelectionWidth=resampleSize*20;
		j_ResampleSize = resampleSize;
	}
	
	
	public GeneManager()
	{
		j_SerializerHelper = new SerializerAdapter(this);
		j_PoolSize=1;
		j_Genomes = new LinkedList<WeightedValue<Genome>>();
		j_SelectionWidth=1;
		j_ResampleSize = 1;
	}
	
	
	
	public GeneManager(int baseFitness)
	{
		j_InitialFitness=baseFitness;
		j_PoolSize=1;
		j_Genomes = new LinkedList<WeightedValue<Genome>>();
		j_SelectionWidth=1;
		j_ResampleSize = 1;
	}
	
	
	
	public void AddGenome(Genome genome)
	{
		j_Genomes.add(new WeightedValue<Genome>(genome, genome.GetFitness()));
		if (j_Genomes.size()>j_PoolSize)
			ResamplePool();
	}
	
	public Genome GetGenome()
	{
		WeightedValue<Genome> w = AITools.ChooseWeightedRandomFair(j_Genomes);
		return w.GetValue();
	}
	
	// this method determines which of the agents will survive the pressure
	
	private void ResamplePool()
	{
		
		LinkedList<WeightedValue<Genome>> sortedList = AITools.SortList(j_Genomes,false);
		
		int i=0;
		double poolWidth=0, fraction;
		for (WeightedValue<Genome> value: sortedList)
		{
			poolWidth+=value.GetWeight();
		}
		fraction = Math.min(1.0,(1.0*j_SelectionWidth)/poolWidth);
		
		int maxWidth=Math.min(j_SelectionWidth, (int)poolWidth);
		WeightedValue[] sample = new WeightedValue[maxWidth];
		j_Genomes = new LinkedList<WeightedValue<Genome>>();
		for (WeightedValue<Genome> value: sortedList)
		{
			for (int j=0;(i<maxWidth)&&(j<value.GetWeight()*fraction);j++)
			{
				sample[i]=value;
				i++;
			}
		}
		Genome newGene;
		WeightedValue<Genome> chosenGenome;
		int chosenIndex;
		for (int k=0;k<j_ResampleSize;k++)
		{
			chosenIndex=(int)(Math.random()*maxWidth);
			chosenGenome=sample[chosenIndex];
			newGene = chosenGenome.GetValue();
			j_Genomes.add(new WeightedValue<Genome>((Genome)newGene.clone(), chosenGenome.GetWeight()));
		}
	}
	
	public int GetBaseFitness()
	{
		if (j_Genomes==null ||j_Genomes.size()==0)
			return j_InitialFitness;
		else
		{
			int totalGenes=0;
			double avgFitness=0;
			for (WeightedValue<Genome> genome: j_Genomes)
			{
				avgFitness=(avgFitness*totalGenes+genome.GetWeight())/(totalGenes+1);
				totalGenes++;
			}
			return (int)avgFitness;
		}
			
	}
	
	
	/****************************************
	 * Serializer Related Fields and methods
	 ****************************************
	 */
	
	public enum LoadState
	{
		LOADING_POOL_PARAMETERS, LOADING_GENOMES,EXIT
	}
	
	
	public enum SaveState
	{
		SAVING_POOL_PARAMETERS, SAVING_GENOMES, EXIT
	}
	
	
	private LoadState j_CurrentState;
	
	/* 
	 * @see com.evolved.automata.experimental.Serializer#AddData(java.lang.String)
	 */
	private SerializerAdapter j_SerializerHelper;
	private String j_UniqueName;
	
	
	
	
	public void SaveData(BufferedWriter writer) {
		SaveState currentState=SaveState.SAVING_POOL_PARAMETERS;
		// Must traverse save state machine in same basic order as load machine is expected to be traversed
		Genome genome;
		while (currentState!=SaveState.EXIT)
		{
			switch (currentState)
			{
				case SAVING_POOL_PARAMETERS:
					int[] parameters = new int[]{j_PoolSize, j_SelectionWidth, j_ResampleSize, j_InitialFitness};
					StaticTools.WriteArray(writer, parameters,",");
					currentState=SaveState.SAVING_GENOMES;
					break;
				case SAVING_GENOMES:
					for (WeightedValue<Genome> g: j_Genomes)
					{
						genome = g.GetValue();
						genome.SetName(j_SerializerHelper.ConstructUniqueName(genome.GetTypeName(), ""+g.GetWeight()));
						genome.Save(writer);
					}
					currentState=SaveState.EXIT;
					break;
			}
		}
	}
	
	
	/*
	 * Use this to load simple data values
	 */
	public void AddData(String dataLine) {
		// Must traverse load state machine in same basic order as data is saved in SaveData
		switch (j_CurrentState)
		{
			case LOADING_POOL_PARAMETERS:
				int[] params = 	StaticTools.ReadStringIntArray(dataLine,",");
				j_PoolSize = params[0];
				j_SelectionWidth=params[1];
				j_ResampleSize = params[2];
				j_InitialFitness = params[3];
				j_CurrentState=LoadState.LOADING_GENOMES;
				break;
			case LOADING_GENOMES:
				j_CurrentState=LoadState.EXIT;
				break;
			
		}
	}

	
	/* 
	 * Use this to add Serializers
	 */
	public void AddData(Serializer object, String instanceId) {
		// Must traverse load state machine in same basic order as data is saved in SaveData
		switch (j_CurrentState)
		{
			case LOADING_POOL_PARAMETERS:
				j_CurrentState=LoadState.LOADING_GENOMES;
				break;
			case LOADING_GENOMES:
				double weight = Double.parseDouble(instanceId);
				WeightedValue<Genome> gene = new WeightedValue<Genome>((Genome)object, weight);

				break;
			
		}
	}

	
	/* 
	 * TODO: Set this to the String value that will be used to create this object in the SerializerAdapter.GetSerializer() class 
	 */
	
	public String GetTypeName() {
		
		return "GeneManager";
	}

	/* 
	 * @see com.evolved.automata.experimental.Serializer#PrepareSerialization()
	 */
	
	public void PrepareSerialization() {
		j_Genomes = new LinkedList<WeightedValue<Genome>>();
		j_CurrentState=LoadState.LOADING_POOL_PARAMETERS;
	}
	
	
	public void Load(BufferedReader reader)
	{
		j_SerializerHelper.Load(reader);
	}
	
	
	public void Load(String fileFullName)
	{
		j_SerializerHelper.Load(fileFullName);
		
	}



	public void Save(String fileFullName)
	{
		j_SerializerHelper.Save(fileFullName);
	}


	public  void Save(BufferedWriter writer)
	{
		j_SerializerHelper.Save(writer);
	}

	
	public void SetName(String uniqueName)
	{
		j_UniqueName=uniqueName;
		j_SerializerHelper.SetUniqueName(uniqueName);
	}

	
	
}
